/**************************
 * Includes
 *
 **************************/

#include <stdio.h>
#include <string>
#include <fstream>
#include <vector>
#include <new>
#include <ctime>

#include <iostream>
#include <fcntl.h>

#include <thread>

#include <GL/glew.h>
#include <GL/gl.h>

#define GLM_FORCE_RADIANS
#include <glm/gtx/quaternion.hpp>

#include "../BSP.h"
#include "../Camera.h"
#include "../GLRenderer.h"
#include "MainWindow.h"
#include "../signalslot.h"

#include "Builder.h"

#include "../GLMesh.h"
#include "../Entities.h"

#include "../leak.h"

using namespace std;



const int MAX_BRUSH_SIDES = 10240;

const float UPDATE_DELTA = 1000.0f/60.0f;

/**************************
 * Function Declarations
 *
 **************************/

void Function();
//void HandleInput();

int num_threads;

Brush* brushes;
unique_ptr<Renderer> g_renderer;

int num_brushes;

float move_vec[3] = {0.0, 0.0, 0.0};
float rot_vec[3] = {0.0, 0.0, 0.0};


cSignal< int, float, float > click_signal;
cSignal< int, float, float > move_signal;
cSignal< int, float, float > release_signal;
cSignal< int, vector<unsigned char>& > key_press_signal;


bool view_update_needed = true;

/**************************
 * WinMain
 *
 **************************/

int main(int argc, char* argv[])
{

	if(SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		//sdldie("Unable to initialize SDL");
	}

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);

	SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
	SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,   32);
	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL,   1);

	num_brushes = 512;
	//brushes = (Brush*)operator new(sizeof(Brush) * num_brushes);
	brushes = new Brush[num_brushes];
	num_brushes = 0;
	
	float cam_pos[3] = {10.0, 0.0, 0.0};
	float cam_target[3] = {0.0, 0.0, 0.0};
	float up[3] = {0.0, 1.0, 0.0};
	Camera cam(cam_pos, cam_target, up);
	cam.Perspective(45, 1.0f, 0.1f, 10000.0f);

	g_render_state.current_cam = &cam;

	MainWindow main_window( "Viewer", 800, 800, NORMAL_WINDOW );

	//ifstream file("C:/Users/Admin/Documents/ObjImp/map_exp2.txt");
	ifstream file("map_src/test155.txt");

	string str;
	string file_contents;

	vector<float> tex_vecs;
	tex_vecs.reserve(512 * 11);

	vector<string> texture_names;

	while(getline(file, str))
	{
		if (str.find("Number of brushes") != string::npos)
		{

		}
		else
		if(str.find("textures") != string::npos)
		{
			while(str.find("{") == string::npos)
			{
				getline(file, str);
			}
			getline(file, str);

			while(str.find("}") == string::npos)
			{
				char cstr[128];
				strcpy (cstr, str.c_str());
				char * p = strtok (cstr, " ()[]\t\r");

				cout << cstr << endl;
				//Renderer::LoadTexture(p);
				texture_names.push_back(p);
				getline(file, str);
			}
		} else
		if(str.find("brush") != string::npos)
		{
			while(str.find("{") == string::npos)
			{
				getline(file, str);
			}
			getline(file, str);

			//float* sides = new float[MAX_BRUSH_SIDES];
			int sides_index = 0;

			vector<float> sides;
			sides.reserve(MAX_BRUSH_SIDES * 3);

			while(str.find("}") == string::npos)
			{
				if(str.find("side") != string::npos)
				{
					char * cstr = new char [str.length() + 1];
					strcpy (cstr, str.c_str());

					char * p = strtok (cstr, " ()");
					
					for(int i = 0; i < 9; i++)
					{
						p = strtok(NULL, " ()");
						//sides[sides_index] = atof(p);
						sides.push_back(stof(p));
						sides_index++;
					}
					for (int i = 0; i < 11; i++)
					{
						p = strtok(NULL, " ()[]\t");
						tex_vecs.push_back(stof(p));
					}

				}

				getline(file, str);
			}

			//cout << "before shrink: " << sides.capacity() << endl;
			sides.shrink_to_fit();
			//cout << "after shrink: " << sides.capacity() << endl;
			//new(&brushes[num_brushes])Brush(sides);
			brushes[num_brushes] = Brush(sides);

			//brushes[num_brushes].InitializeMesh();

			num_brushes++;
			//sides.clear();
			//delete[] sides;
			
		} else
		if(str.find("static_mesh") != string::npos)
		{
			while(str.find("{") == string::npos)
			{
				getline(file, str);
			}
			getline(file, str);

			g_static_meshes.emplace_back();

			char cstr[128];

			while(str.find("}") == string::npos)
			{
				if(str.find("name") != string::npos)
				{
					strcpy (cstr, str.c_str());
					char * p = strtok (cstr, " ()[]\t");
					p = strtok(NULL, " ()[]\t");

					g_static_meshes[g_static_meshes.size() - 1].set_name(p);
				} else
				if(str.find("file") != string::npos)
				{
					strcpy (cstr, str.c_str());
					char * p = strtok (cstr, " ()[]\t\r");
					p = strtok(NULL, " ()[]\t\r");

					g_static_meshes[g_static_meshes.size() - 1].LoadFromFile(p);
				} else
				if(str.find("angles") != string::npos)
				{
					glm::vec3 axis;
					strcpy (cstr, str.c_str());
					char * p = strtok (cstr, " ()[]\t");

					p = strtok(NULL, " ()[]\t");
					axis[0] = atof(p);
					p = strtok(NULL, " ()[]\t");
					axis[1] = atof(p);
					p = strtok(NULL, " ()[]\t");
					axis[2] = atof(p);

					float angle;
					p = strtok(NULL, " ()[]\t");
					angle = atof(p);
					angle = glm::radians(angle);

					glm::quat q = glm::angleAxis(angle, axis);

					g_static_meshes[g_static_meshes.size() - 1].SetMatrix(glm::toMat4(q));
					//g_static_meshes[g_static_meshes.size() - 1].SetAngles(angles[0], angles[1], angles[2]);

				} else
				if(str.find("pos") != string::npos)
				{
					glm::vec3 pos;
					strcpy (cstr, str.c_str());
					char * p = strtok (cstr, " ()[]\t");

					p = strtok(NULL, " ()[]\t");
					pos[0] = atof(p);
					p = strtok(NULL, " ()[]\t");
					pos[1] = atof(p);
					p = strtok(NULL, " ()[]\t");
					pos[2] = atof(p);

					//g_static_meshes[g_static_meshes.size() - 1].Move(pos[0], pos[1], pos[2]);
					g_static_meshes[g_static_meshes.size() - 1].SetPos(pos[0], pos[1], pos[2]);
				}
				getline(file, str);
			}
		} else
		if(str.find("sky_camera") != string::npos)
		{
			g_entities.emplace_back(new SkyCamera());
			g_entities[g_entities.size()-1]->Import(file);
		}
	}
	
	vector<thread> threads;

	int num_threads = min(thread::hardware_concurrency(), (unsigned int)num_brushes);
	for( int i = 0;  i < num_threads; i++ )
	{
		threads.push_back( thread([&](int id)->void
				{
					for( int i = id; i < num_brushes; i+=num_threads )
					{
						brushes[i].InitializeMesh();
						brushes[i].UpdateUVs();
					}
				}, i) );
	}

	for( int i = 0;  i < num_threads; i++ )
	{
		threads[i].join();
	}
	

	auto vec_it = tex_vecs.begin();
	for (int i = 0; i < num_brushes; i++)
	{
		brushes[i].SetTexVec(vec_it, texture_names);
		brushes[i].UpdateUVs();
	}

	tex_vecs.clear();

	brushes = CSGUnion(brushes, num_brushes);

	NewFunc(brushes, num_brushes);

	//brushes[0].ClipToBrush(brushes[1], true);

	//while(g_num_brushes_done != num_brushes)
	//{
		//cout << g_num_brushes_done << " " << num_brushes << endl;
		//Sleep(400);
	//}


	g_prev_time = SDL_GetTicks();



	//MyBaseWindow main_window( 800, 800, BLUR_BEHIND | FULL_SCR_ENABLED | RESIZABLE, WndProc );
	main_window.Show();

	Renderer::Init( &g_render_state );
	
	g_renderer.reset(new Renderer(800, 800));
	g_renderer->set_DrawFunc(Function);


	click_signal.Connect( main_window.click );
	move_signal.Connect( main_window.move );
	release_signal.Connect( main_window.release ); 
	key_press_signal.Connect( main_window.key_press ); 


	NewFunc11();

	//for(int i = 0; i < num_brushes; i++)
	//{
	//	brushes[i].UpdateUVs();
	//}

    /* program main loop */
    while (!g_quit)
    {
    	FireSignals(click_signal, release_signal, move_signal, key_press_signal);

		g_cur_time = SDL_GetTicks();
		g_delta_time = g_cur_time - g_prev_time;
		if(g_delta_time > 200)
		{
			g_delta_time = 200;
		}
		g_prev_time = g_cur_time;

		g_time_acc += g_delta_time;

		while(g_time_acc >= UPDATE_DELTA)
		{
			cam.Move(move_vec, UPDATE_DELTA);

			g_time_acc -= UPDATE_DELTA;
		}

		cam.UpdatePlanes();

		main_window.MakeCurrent();

		if( view_update_needed )
		{

			main_window.SetupMainViewPort( cam );

			glClearColor(0.0, 0.0, 0.0, 0.0);
			glClear(GL_COLOR_BUFFER_BIT);

			g_renderer->Draw();

			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

		}

		glClearColor(0.0, 0.0, 0.0, 0.0);
		glClear(GL_COLOR_BUFFER_BIT);

		glDisable(GL_DEPTH_TEST);
		g_renderer->SwitchProgram(SCREEN_DRAW_PROGRAM);

		main_window.Draw();

		main_window.Apply();
    }

	
	/*if(brushes){
		for(int i = num_brushes-1; i >= 0; i--){
			brushes[i].~Brush();
		}
		operator delete[](brushes);
	}*/
    delete[] brushes;

	SDL_Quit();
}




void DrawOutside();

void Function() {

	//Renderer::SwitchProgram( CONSTANT_PROGRAM );
	Renderer::SwitchProgram( MAIN_PROGRAM );

	float view_proj_mat[16];
	matrixMultiplyMM(view_proj_mat, (float*)g_render_state.current_cam->projection_matrix(), (float*)g_render_state.current_cam->view_matrix());

	glUniformMatrix4fv(g_render_state.current_program->uniform(MVP_UNIFORM), 1, GL_FALSE, view_proj_mat);

	/*for(int i = 0; i < num_brushes; i++)
	{
		brushes[i].Draw(i);
		brushes[i].DrawWTexture();
	}*/

	DrawOutside();

}


void DrawOutside() {
	
	//root_node->Draw();
	/*
	for(int i = 0; i < nodes_array.size(); i++)
	{
		if(nodes_array[i])
		{
			if(nodes_array[i]->has_polys())
			{
				nodes_array[i]->Draw();
			}
		}
	}
	*/
	//cout << g_portals.size() << endl;

	//for(int i = 0; i < nodes_array.size(); i++)
	for(vector<BSPNode*>::iterator node = nodes_array.begin(); node != nodes_array.end(); node++)
	{
		if( (*node)->has_polys() )
		{
			(*node)->set_drawn(false);
		}
	}
	BSPNode::num_drawn = 0;
	/*
	int num_drawn = 0;
	for(int idx = 0; idx < g_portals.size(); idx++)
	{
	//int idx = 0;

		if( !g_render_state.current_cam->IsInFrustum(g_portals[idx]->polygon()) )
		{
			continue;
		}

		if( !g_portals[idx]->front_node()->drawn() )
		{
			g_portals[idx]->front_node()->Draw();
		}
		if( !g_portals[idx]->back_node()->drawn() )
		{
			g_portals[idx]->back_node()->Draw();
		}
		g_portals[idx]->Draw();
		num_drawn++;
	}
	*/
	//cout << "num_drawn: " << num_drawn << "        ";
	//cout << '\r';
	
	int current_zone = root_node->Traverse(g_render_state.current_cam->world_position());
	nodes_array[current_zone]->DrawPortals();
	
	glUniform3f(g_render_state.current_program->uniform(COL_UNIFORM), 1.0f, 1.0f, 1.0f);

	std::set<GLStaticMesh*> meshes_to_draw;
	nodes_array[current_zone]->ListMeshes(meshes_to_draw);
	for(auto& p_mesh : meshes_to_draw)
	{
		p_mesh->Draw();
	}
	//if(!meshes_to_draw.empty())
	//meshes_to_draw.clear();
	
	//cout << "id: " << BSPNode::num_drawn << "        ";
	//cout << '\r';

	//nodes_array[current_zone]->Draw();
	

	/*
	for(int i = 0; i < total_polygon_list.size(); i++)
	{
		glUniform3f(g_render_state.current_program->uniform(COL_UNIFORM), color_table[i][0], color_table[i][1], color_table[i][2]);
		
		glEnableVertexAttribArray(g_render_state.current_program->attribute(POSITION_ATTRIBUTE));
		glVertexAttribPointer(g_render_state.current_program->attribute(POSITION_ATTRIBUTE), 3, GL_FLOAT, GL_FALSE, 0, &total_polygon_list[i].points[0]);

		//if( i == best_div_poly)
		//{
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		glDrawArrays(GL_TRIANGLE_FAN, 0, total_polygon_list[i].points.size() / 3);

		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		//}
	}
	*/
	/*
	for(int i = 0; i < g_front_poly_list.size(); i++)
	{
		glEnableVertexAttribArray(g_render_state.current_program->attribute(POSITION_ATTRIBUTE));
		glVertexAttribPointer(g_render_state.current_program->attribute(POSITION_ATTRIBUTE), 3, GL_FLOAT, GL_FALSE, 0, &g_front_poly_list[i].points[0]);

		glDrawArrays(GL_TRIANGLE_FAN, 0, g_front_poly_list[i].points.size() / 3);
	}
	
	for(int i = 0; i < g_back_poly_list.size(); i++)
	{
		glEnableVertexAttribArray(g_render_state.current_program->attribute(POSITION_ATTRIBUTE));
		glVertexAttribPointer(g_render_state.current_program->attribute(POSITION_ATTRIBUTE), 3, GL_FLOAT, GL_FALSE, 0, &g_back_poly_list[i].points[0]);

		glDrawArrays(GL_TRIANGLE_FAN, 0, g_back_poly_list[i].points.size() / 3);
	}
	*/
}
